/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************

	Classes for the hex editor.

***************************************************************************/
#include "ched.h"
ASSERTNAME


#define kcbMaxLineDch 16

/***************************************************************************
	A document class that holds a stream and is naturally displayed by the
	hex editor (DCH).  Used for the clipboard.
***************************************************************************/
typedef class DHEX *PDHEX;
#define DHEX_PAR DOCB
#define kclsDHEX 'DHEX'
class DHEX : public DHEX_PAR
	{
	RTCLASS_DEC
	ASSERT
	MARKMEM

protected:
	BSF _bsf;

	DHEX(PDOCB pdocb = pvNil, ulong grfdoc = fdocNil)
		: DHEX_PAR(pdocb, grfdoc) {}

public:
	static PDHEX PdhexNew(void);

	PBSF Pbsf(void)
		{ return &_bsf; }

	virtual PDDG PddgNew(PGCB pgcb);
	};


RTCLASS(DCH)
RTCLASS(DOCH)
RTCLASS(DHEX)


/***************************************************************************
	Static method to create a new text stream document to be displayed
	by the hex editor.
***************************************************************************/
PDHEX DHEX::PdhexNew(void)
{
	PDHEX pdhex;

	if (pvNil == (pdhex = NewObj DHEX()))
		return pvNil;

	return pdhex;
}


/***************************************************************************
	Create a new DCH displaying this stream.
***************************************************************************/
PDDG DHEX::PddgNew(PGCB pgcb)
{
	return DCH::PdchNew(this, &_bsf, fFalse, pgcb);
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of a DHEX.
***************************************************************************/
void DHEX::AssertValid(ulong grf)
{
	DHEX_PAR::AssertValid(0);
	AssertPo(&_bsf, 0);
}


/***************************************************************************
	Mark memory for the DHEX.
***************************************************************************/
void DHEX::MarkMem(void)
{
	AssertValid(0);
	DHEX_PAR::MarkMem();
	MarkMemObj(&_bsf);
}
#endif //DEBUG


/***************************************************************************
	Constructor for the DCH.
***************************************************************************/
DCH::DCH(PDOCB pdocb, PBSF pbsf, bool fFixed, PGCB pgcb) : DCLB(pdocb, pgcb)
{
	_pbsf = pbsf;
	_cbLine = kcbMaxLineDch;
	_dypHeader = _dypLine + 1;
	_fFixed = FPure(fFixed);
}


/***************************************************************************
	Static method to create a new DCH.
***************************************************************************/
PDCH DCH::PdchNew(PDOCB pdocb, PBSF pbsf, bool fFixed, PGCB pgcb)
{
	PDCH pdch;

	if (pvNil == (pdch = NewObj DCH(pdocb, pbsf, fFixed, pgcb)))
		return pvNil;

	if (!pdch->_FInit())
		{
		ReleasePpo(&pdch);
		return pvNil;
		}
	pdch->Activate(fTrue);

	AssertPo(pdch, 0);
	return pdch;
}


/***************************************************************************
	We're being activated or deactivated, invert the sel.
***************************************************************************/
void DCH::_Activate(bool fActive)
{
	AssertThis(0);
	RC rc;

	DDG::_Activate(fActive);
	GetRc(&rc, cooLocal);
	rc.ypBottom = _dypHeader;
	InvalRc(&rc);
	_SwitchSel(fActive);
}


/***************************************************************************
	Draw the Hex doc in the port.
***************************************************************************/
void DCH::Draw(PGNV pgnv, RC *prcClip)
{
	AssertThis(0);
	AssertPo(pgnv, 0);
	AssertVarMem(prcClip);
	STN stn;
	byte rgb[kcbMaxLineDch];
	RC rc, rcSrc;
	long xp, yp, cb, ib, cbT, ibT;
	byte bT;

	pgnv->ClipRc(prcClip);
	pgnv->GetRcSrc(&rcSrc);
	pgnv->SetOnn(_onn);

	if (prcClip->ypTop < _dypHeader)
		_DrawHeader(pgnv);

	Assert(_cbLine <= size(rgb), "lines too long");
	cb = _pbsf->IbMac();
	xp = _XpFromIch(0);
	ib = LwMul(_cbLine, _LnFromYp(LwMax(_dypHeader, prcClip->ypTop)));
	yp = _YpFromIb(ib);
	rc.xpLeft = _XpFromCb(_cbLine, fFalse);
	rc.xpRight = prcClip->xpRight;
	rc.ypTop = yp;
	rc.ypBottom = prcClip->ypBottom;
	pgnv->FillRc(&rc, kacrWhite);
	if (xp > 0)
		{
		//erase to the left of the text
		rc.xpLeft = 0;
		rc.xpRight = xp;
		pgnv->FillRc(&rc, kacrWhite);
		}

	for ( ; ib < cb && yp < prcClip->ypBottom; ib += _cbLine)
		{
		cbT = LwMin(_cbLine, cb - ib);
		_pbsf->FetchRgb(ib, cbT, rgb);

		//first comes the address of the first byte of the line
		stn.FFormatSz(PszLit("%08x "), ib);

		//now add the line's bytes in hex, with a space after every
		//four bytes
		for (ibT = 0; ibT < cbT; ibT++)
			{
			bT = rgb[ibT];
			if ((ibT & 0x03) == 0)
				stn.FAppendCh(kchSpace);
			stn.FAppendCh(vrgchHex[(bT >> 4) & 0x0F]);
			stn.FAppendCh(vrgchHex[bT & 0x0F]);
			}
		//pad the line with spaces
		if (ibT < _cbLine)
			{
			ibT = _cbLine - ibT;
			ibT = 2 * ibT + ibT / 4;
			while (ibT-- > 0)
				stn.FAppendCh(kchSpace);
			}
		stn.FAppendSz(PszLit("  "));

		//now comes the ascii characters.
		for (ibT = 0; ibT < cbT; ibT++)
			{
			bT = rgb[ibT];
			if (bT < 32 || bT == 0x7F)
				bT = '?';
			stn.FAppendCh((achar)bT);
			}
		//pad the line with spaces
		while (ibT++ < _cbLine)
			stn.FAppendCh(kchSpace);

		pgnv->DrawStn(&stn, xp, yp, kacrBlack, kacrWhite);
		yp += _dypLine;
		}
	if (yp < prcClip->ypBottom)
		{
		rc = rcSrc;
		rc.ypTop = yp;
		pgnv->FillRc(&rc, kacrWhite);
		}

	//draw the selection
	if (_fSelOn)
		_InvertSel(pgnv);
}


/***************************************************************************
	Draw the header for the DCH.
***************************************************************************/
void DCH::_DrawHeader(PGNV pgnv)
{
	STN stn;
	RC rc, rcSrc;

	pgnv->SetOnn(_onn);
	pgnv->GetRcSrc(&rcSrc);

	//erase the first part of the line
	rc.xpLeft = 0;
	rc.xpRight = _XpFromIch(0);
	rc.ypTop = 0;
	rc.ypBottom = _dypLine;
	pgnv->FillRc(&rc, kacrWhite);

	//draw the text
	stn.FFormatSz(PszLit("%08x"), _pbsf->IbMac());
	pgnv->DrawStn(&stn, rc.xpRight, 0, kacrBlack, kacrWhite);

	//erase the rest of the line
	rc.xpLeft = _XpFromIch(8);
	rc.xpRight = rcSrc.xpRight;
	pgnv->FillRc(&rc, kacrWhite);

	//draw the _fHex Marker
	rc.xpLeft = _XpFromCb(0, _fHexSel);
	rc.xpRight = rc.xpLeft + rc.Dyp();
	rc.Inset(rc.Dyp() / 6, rc.Dyp() / 6);
	if (_fActive)
		pgnv->FillRc(&rc, kacrBlack);
	else
		pgnv->FillRcApt(&rc, &vaptGray, kacrBlack, kacrWhite);

	//draw the line seperating the header from the data
	rc = rcSrc;
	rc.ypTop = _dypHeader - 1;
	rc.ypBottom = _dypHeader;
	pgnv->FillRc(&rc, kacrBlack);
}


/***************************************************************************
	Handle key input.
***************************************************************************/
bool DCH::FCmdKey(PCMD_KEY pcmd)
{
	AssertThis(0);
	ulong grfcust;
	long dibSel, dibDel, ibLim;
	long cact;
	CMD cmd;
	byte rgb[64], bT;
	bool fRight = fFalse;

	// keep fetching characters until we get a cursor key, delete key or
	// until the buffer is full.
	dibSel = 0;
	dibDel = 0;
	ibLim = 0;
	do
		{
		switch (pcmd->vk)
			{
		case kvkHome:
			dibSel = -_pbsf->IbMac() - ibLim - 1;
			break;
		case kvkEnd:
			dibSel = _pbsf->IbMac() + ibLim + 1;
			break;
		case kvkLeft:
			dibSel = -1;
			break;
		case kvkRight:
			dibSel = 1;
			fRight = fTrue;
			break;
		case kvkUp:
			dibSel = -_cbLine;
			fRight = _fRightSel;
			break;
		case kvkDown:
			dibSel = _cbLine;
			fRight = _fRightSel;
			break;

		case kvkDelete:
			if (!_fFixed)
				dibDel = 1;
			break;
		case kvkBack:
			if (!_fFixed)
				dibDel = -1;
			break;

		default:
			if (chNil == pcmd->ch)
				break;
			if (!_fHexSel)
				bT = (byte)pcmd->ch;
			else
				{
				//hex typing
				if (FIn(pcmd->ch, '0', '9' + 1))
					bT = pcmd->ch - '0';
				else if (FIn(pcmd->ch, 'A', 'F' + 1))
					bT = pcmd->ch - 'A' + 10;
				else if (FIn(pcmd->ch, 'a', 'f' + 1))
					bT = pcmd->ch - 'a' + 10;
				else
					break;
				}
			for (cact = 0; cact < pcmd->cact && ibLim < size(rgb); cact++)
				rgb[ibLim++] = bT;
			break;
			}

		grfcust = pcmd->grfcust;
		pcmd = (PCMD_KEY)&cmd;
		}
	while (0 == dibSel && 0 == dibDel && ibLim < size(rgb) && vpcex->FGetNextKey(&cmd));

	if (ibLim > 0)
		{
		//have some characters to insert
		if (!_fHexSel)
			{
			//just straight characters to insert
			_FReplace(rgb, ibLim, _ibAnchor, _ibOther);
			}
		else
			{
			//hex typing
			byte bT;
			long ibSrc, ibDst;
			long ibAnchor = _ibAnchor;

			if (_fHalfSel && ibAnchor > 0)
				{
				//complete the byte
				_pbsf->FetchRgb(--ibAnchor, 1, &bT);
				rgb[0] = (bT & 0xF0) | (rgb[0] & 0x0F);
				ibSrc = 1;
				}
			else
				ibSrc = 0;

			for (ibDst = ibSrc; ibSrc + 1 < ibLim; ibSrc += 2)
				rgb[ibDst++] = (rgb[ibSrc] << 4) | (rgb[ibSrc + 1] & 0x0F);

			if (ibSrc < ibLim)
				{
				Assert(ibSrc + 1 == ibLim, 0);
				rgb[ibDst++] = rgb[ibSrc] << 4;
				}

			_FReplace(rgb, ibDst, ibAnchor, _ibOther, ibSrc < ibLim);
			}
		}

	if (dibSel != 0)
		{
		//move the selection
		if (grfcust & fcustShift)
			{
			//extend selection
			_SetSel(_ibAnchor, _ibOther + dibSel, fRight);
			_ShowSel();
			}
		else
			{
			long ibOther = _ibOther;
			long ibAnchor = _ibAnchor;

			if (_fHalfSel)
				{
				if (dibSel < 0)
					{
					ibAnchor--;
					fRight = fFalse;
					}
				else
					fRight = fTrue;
				}
			else if (ibAnchor == ibOther)
				ibAnchor += dibSel;
			else if ((dibSel > 0) != (ibAnchor > ibOther))
				{
				ibAnchor = ibOther;
				fRight = dibSel > 0;
				}
			_SetSel(ibAnchor, ibAnchor, fRight);
			_ShowSel();
			}
		}
	else if (dibDel != 0)
		{
		if (_ibAnchor != _ibOther)
			dibDel = _ibOther - _ibAnchor;
		else
			dibDel = LwBound(_ibAnchor + dibDel, 0, _pbsf->IbMac() + 1) - _ibAnchor;
		if (dibDel != 0)
			_FReplace(pvNil, 0, _ibAnchor, _ibAnchor + dibDel);
		}

	return fTrue;
}


/***************************************************************************
	Replaces the bytes between ib1 and ib2 with the given bytes.
***************************************************************************/
bool DCH::_FReplace(byte *prgb, long cb, long ib1, long ib2, bool fHalfSel)
{
	_SwitchSel(fFalse);
	SortLw(&ib1, &ib2);
	if (_fFixed)
		{
		cb = LwMin(cb, _pbsf->IbMac() - ib1);
		ib2 = ib1 + cb;
		}
	if (!_pbsf->FReplace(prgb, cb, ib1, ib2 - ib1))
		return fFalse;

	_InvalAllDch(ib1, cb, ib2 - ib1);
	ib1 += cb;
	if (fHalfSel)
		_SetHalfSel(ib1);
	else
		_SetSel(ib1, ib1, fFalse /*REVIEW shonk*/);
	_ShowSel();
	return fTrue;
}


/***************************************************************************
	Invalidate all DCHs on this byte stream.  Also dirties the document.
	Should be called by any code that edits the document.
***************************************************************************/
void DCH::_InvalAllDch(long ib, long cbIns, long cbDel)
{
	AssertThis(0);
	long ipddg;
	PDDG pddg;

	//mark the document dirty
	_pdocb->SetDirty();

	//inform the DCDs
	for (ipddg = 0; pvNil != (pddg = _pdocb->PddgGet(ipddg)); ipddg++)
		{
		if (pddg->FIs(kclsDCH))
            ((PDCH)pddg)->_InvalIb(ib, cbIns, cbDel);
		}
}


/***************************************************************************
	Invalidate the display from ib to the end of the display.  If we're
	the active DCH, also redraw.
***************************************************************************/
void DCH::_InvalIb(long ib, long cbIns, long cbDel)
{
	AssertThis(0);
	Assert(!_fSelOn, "why is the sel on during an invalidation?");
	RC rc;
	long ibAnchor, ibOther;

	//adjust the sel
	ibAnchor = _ibAnchor;
	ibOther = _ibOther;
	FAdjustIv(&ibAnchor, ib, cbIns, cbDel);
	FAdjustIv(&ibOther, ib, cbIns, cbDel);
	if (ibAnchor != _ibAnchor || ibOther != _ibOther)
		_SetSel(ibAnchor, ibOther, _fRightSel);

	//caclculate the invalid rectangle
	GetRc(&rc, cooLocal);
	rc.ypTop = _YpFromIb(ib);
	if (cbIns == cbDel)
		rc.ypBottom = rc.ypTop + _dypLine;

	if (rc.FEmpty())
		return;

	if (_fActive)
		{
		ValidRc(&rc, kginDraw);
		InvalRc(&rc, kginDraw);
		}
	else
		InvalRc(&rc);

	if (cbIns != cbDel)
		{
		//invalidate the length
		GetRc(&rc, cooLocal);
		rc.xpLeft = _XpFromIch(0);
		rc.xpRight = _XpFromIch(8);
		rc.ypTop = 0;
		rc.ypBottom = _dypHeader;
		InvalRc(&rc);
		}
}


/***************************************************************************
	Turn the selection on or off.
***************************************************************************/
void DCH::_SwitchSel(bool fOn)
{
	if (FPure(fOn) != FPure(_fSelOn))
		{
		GNV gnv(this);
		_InvertSel(&gnv);
		_fSelOn = FPure(fOn);
		}
}


/***************************************************************************
	Make sure the ibOther of the selection is visible.  If possible, show
	both ends of the selection.
***************************************************************************/
void DCH::_ShowSel(void)
{
	long ln, lnHope, cln, dscv;
	RC rc;

	//find the line we definitely need to show
	ln = _ibOther / _cbLine;
	if (_ibOther % _cbLine == 0 && ln > 0)
		{
		//may have to adjust ln down by one
		if (_ibAnchor < _ibOther || _ibAnchor == _ibOther && _fRightSel)
			ln--;
		}

	//find the other end of the selection - which we hope to be able to show
	lnHope = _ibAnchor / _cbLine;

	_GetContent(&rc);
	cln = LwMax(1, rc.Dyp() / _dypLine);
	if (LwAbs(ln - lnHope) >= cln)
		lnHope = ln; //can't show both

	if (FIn(ln, _scvVert, _scvVert + cln) &&
			FIn(lnHope, _scvVert, _scvVert + cln))
		{
		//both are showing
		return;
		}

	Assert(LwAbs(lnHope - ln) < cln, "ln and lnHope too far apart");
	SortLw(&ln, &lnHope);
	if (ln < _scvVert)
		dscv = ln - _scvVert;
	else
		{
		dscv = lnHope - _scvVert - cln + 1;
		Assert(dscv > 0, "bad dscv (bad logic above)");
		}
	_Scroll(scaNil, scaToVal, 0, _scvVert + dscv);
}


/***************************************************************************
	Invert the selection.  Doesn't touch _fSelOn.
***************************************************************************/
void DCH::_InvertSel(PGNV pgnv)
{
	Assert(!_fFixed || _ibAnchor == _ibOther, "non-ins sel in fixed");
	long cb;
	RC rcClip;
	RC rc, rcT;

	_GetContent(&rcClip);
	if (_fFixed && _pbsf->IbMac() > 0 && !_fHalfSel)
		{
		rc.xpLeft = _XpFromIb(_ibAnchor, fTrue);
		rc.xpRight = rc.xpLeft + 2 * _dxpChar;
		rc.ypTop = _YpFromIb(_ibAnchor);
		rc.ypBottom = rc.ypTop + _dypLine;
		if (rcT.FIntersect(&rc, &rcClip))
			pgnv->HiliteRc(&rcT, kacrWhite);

		rc.xpLeft = _XpFromIb(_ibAnchor, fFalse);
		rc.xpRight = rc.xpLeft + _dxpChar;
		if (rcT.FIntersect(&rc, &rcClip))
			pgnv->HiliteRc(&rcT, kacrWhite);
		}
	else if (_ibAnchor == _ibOther)
		{
		//insertion or half sel
		Assert(!_fHalfSel || _fRightSel, "_fHalfSel set but not _fRightSel");
		cb = _ibAnchor % _cbLine;
		if (_fRightSel && cb == 0 && _ibAnchor > 0)
			{
			rc.ypTop = _YpFromIb(_ibAnchor - 1);
			cb = _cbLine;
			}
		else
			rc.ypTop = _YpFromIb(_ibAnchor);
		rc.ypBottom = rc.ypTop + _dypLine;

		//do the hex sel
		rc.xpLeft = _XpFromCb(cb, fTrue, _fRightSel);
		if (_fHalfSel && _ibAnchor > 0)
			{
			rc.xpRight = rc.xpLeft;
			rc.xpLeft -= _dxpChar;
			if (rcT.FIntersect(&rc, &rcClip))
				pgnv->HiliteRc(&rcT, kacrWhite);
			}
		else
			{
			rc.xpRight = --rc.xpLeft + 2;
			if (rcT.FIntersect(&rc, &rcClip))
				pgnv->FillRc(&rcT, kacrInvert);
			}

		//do the ascii sel
		rc.xpLeft = _XpFromCb(cb, fFalse) - 1;
		rc.xpRight = rc.xpLeft + 2;
		if (rcT.FIntersect(&rc, &rcClip))
			pgnv->FillRc(&rcT, kacrInvert);
		}
	else
		{
		_InvertIbRange(pgnv, _ibAnchor, _ibOther, fTrue);
		_InvertIbRange(pgnv, _ibAnchor, _ibOther, fFalse);
		}
}


/***************************************************************************
	Inverts a range on screen.  Does not mark insertion bars or half sels.
***************************************************************************/
void DCH::_InvertIbRange(PGNV pgnv, long ib1, long ib2, bool fHex)
{
	long ibMin, ibMac;
	long xp2, yp2;
	RC rc, rcT, rcClip;

	ibMin = _scvVert * _cbLine;
	ibMac = _pbsf->IbMac();
	ib1 = LwBound(ib1, ibMin, ibMac + 1);
	ib2 = LwBound(ib2, ibMin, ibMac + 1);

	if (ib1 == ib2)
		return;
	SortLw(&ib1, &ib2);

	_GetContent(&rcClip);
	rc.xpLeft = _XpFromIb(ib1, fHex);
	rc.ypTop = _YpFromIb(ib1);
	xp2 = _XpFromIb(ib2, fHex);
	yp2 = _YpFromIb(ib2);

	rc.ypBottom = rc.ypTop + _dypLine;
	if (yp2 == rc.ypTop)
		{
		//only one line involved
		rc.xpRight = xp2;
		if (rcT.FIntersect(&rc, &rcClip))
			pgnv->HiliteRc(&rcT, kacrWhite);
		return;
		}

	//invert the sel on the first line
	rc.xpRight = _XpFromCb(_cbLine, fHex);
	if (rcT.FIntersect(&rc, &rcClip))
		pgnv->HiliteRc(&rcT, kacrWhite);

	//invert the main rectangular block
	rc.xpLeft = _XpFromCb(0, fHex);
	rc.ypTop += _dypLine;
	rc.ypBottom = yp2;
	if (rcT.FIntersect(&rc, &rcClip))
		pgnv->HiliteRc(&rcT, kacrWhite);

	//invert the last line
	rc.ypTop = yp2;
	rc.ypBottom = yp2 + _dypLine;
	rc.xpRight = xp2;
	if (rcT.FIntersect(&rc, &rcClip))
		pgnv->HiliteRc(&rcT, kacrWhite);
}


/***************************************************************************
	Select the second half of the byte before ib.
***************************************************************************/
void DCH::_SetHalfSel(long ib)
{
	ib = LwBound(ib, 0, _pbsf->IbMac() + 1);
	if (ib == 0)
		{
		_SetSel(ib, ib, fFalse);
		return;
		}

	GNV gnv(this);
	if (_fSelOn)
		{
		//turn off the sel
		_InvertSel(&gnv);
		_fSelOn = fFalse;
		}
	_ibAnchor = _ibOther = ib;
	_fHalfSel = fTrue;
	_fRightSel = fTrue;
	if (_fActive)
		{
		_InvertSel(&gnv);
		_fSelOn = fTrue;
		}
}


/***************************************************************************
	Set the selection.  fRight is ignored for non-insertion bar selections.
***************************************************************************/
void DCH::_SetSel(long ibAnchor, long ibOther, bool fRight)
{
	long ibMac = _pbsf->IbMac();
	GNV gnv(this);

	if (_fFixed && ibMac > 0)
		{
		ibOther = ibAnchor = LwBound(ibOther, 0, ibMac);
		fRight = fFalse;
		}
	else
		{
		ibAnchor = LwBound(ibAnchor, 0, ibMac + 1);
		ibOther = LwBound(ibOther, 0, ibMac + 1);
		if (ibAnchor == ibOther)
			{
			if (fRight && ibAnchor == 0)
				fRight = fFalse;
			else if (!fRight && ibAnchor == ibMac)
				fRight = fTrue;
			}
		else
			fRight = fFalse;
		}

	if (!_fHalfSel && ibAnchor == _ibAnchor && ibOther == _ibOther &&
			FPure(fRight) == FPure(_fRightSel))
		{
		goto LDrawSel;
		}

	if (_fSelOn)
		{
		if (_ibAnchor != ibAnchor || _ibAnchor == _ibOther ||
				ibAnchor == ibOther)
			{
			_InvertSel(&gnv);
			_fSelOn = fFalse;
			}
		else
			{
			//they have the same anchor and neither is an insertion
			_InvertIbRange(&gnv, _ibOther, ibOther, fTrue);
			_InvertIbRange(&gnv, _ibOther, ibOther, fFalse);
			}
		}

	_ibAnchor = ibAnchor;
	_ibOther = ibOther;
	_fRightSel = FPure(fRight);
	_fHalfSel = fFalse;

LDrawSel:
	if (!_fSelOn && _fActive)
		{
		_InvertSel(&gnv);
		_fSelOn = fTrue;
		}
}


/***************************************************************************
	Changes the selection type from hex to ascii or vice versa.
***************************************************************************/
void DCH::_SetHexSel(bool fHex)
{
	if (FPure(fHex) == FPure(_fHexSel))
		return;
	_fHexSel = FPure(fHex);

	GNV gnv(this);
	_DrawHeader(&gnv);
}


/***************************************************************************
	Find the column for the given horizontal byte position.  cb is the number
	of bytes in from the left edge.  fHex indicates whether we want the
	position in the hex area or the ascii area.  fNoTrailSpace is ignored if
	fHex is false.  If fHex is true, fNoTrailSpace indicates whether a
	trailing space should be included (if the cb is divisible by 4).
***************************************************************************/
long DCH::_IchFromCb(long cb, bool fHex, bool fNoTrailSpace)
{
	AssertIn(cb, 0, _cbLine + 1);

	//skip over the address
	long ich = 10;

	if (fHex)
		{
		//account for the spaces every four hex digits
		ich += 2 * cb + cb / 4;
		if (fNoTrailSpace && (cb % 4) == 0 && cb > 0)
			ich--;
		}
	else
		{
		//skip over the hex area
		ich += 2 * _cbLine + _cbLine / 4 + 1 + cb;
		}
	return ich;
}


/***************************************************************************
	Find the xp for the given byte.  fHex indicates whether we want the
	postion in the hex area or the ascii area.
***************************************************************************/
long DCH::_XpFromIb(long ib, bool fHex)
{
	return _XpFromIch(_IchFromCb(ib % _cbLine, fHex));
}


/***************************************************************************
	Find the xp for the given horizontal byte position.  cb is the number
	of bytes in from the left edge.  fHex indicates whether we want the
    postion in the hex area or the ascii area.
***************************************************************************/
long DCH::_XpFromCb(long cb, bool fHex, bool fNoTrailSpace)
{
	return _XpFromIch(_IchFromCb(cb, fHex, fNoTrailSpace));
}


/***************************************************************************
	Find the yp for the given byte.
***************************************************************************/
long DCH::_YpFromIb(long ib)
{
	AssertIn(ib, 0, kcbMax);
	return LwMul((ib / _cbLine) - _scvVert, _dypLine) + _dypHeader;
}


/***************************************************************************
	Finds the byte that the given point is over.  *ptHex is both input and
	output.  If *ptHex is tMaybe on input, it will be set to tYes or tNo on
	output (unless the point is not in the edit area of the DCH).  If *ptHex
	is tYes or tNo on input, the ib is determined using *ptHex.
***************************************************************************/
long DCH::_IbFromPt(long xp, long yp, bool *ptHex, bool *pfRight)
{
	AssertVarMem(ptHex);
	AssertNilOrVarMem(pfRight);

	RC rc;
	long cbMin, cbLim, cb, ib;
	long xpFind, xpT;
	bool fHex;

	_GetContent(&rc);
	if (!rc.FPtIn(xp, yp))
		return ivNil;

	if (*ptHex == tMaybe)
		{
		xpT = (_XpFromCb(_cbLine, fTrue, fTrue)  + _XpFromCb(0, fFalse)) / 2;
		if (xp <= xpT)
			*ptHex = tYes;
		else
			*ptHex = tNo;
		}

	xpFind = xp - _dxpChar;
	if (*ptHex == tYes)
		{
		if (_fFixed)
			xpFind -= _dxpChar;
		fHex = fTrue;
		}
	else
		{
		if (!_fFixed)
			xpFind = xp - _dxpChar / 2;
		fHex = fFalse;
		}

	for (cbMin = 0, cbLim = _cbLine; cbMin < cbLim; )
		{
		cb = (cbMin + cbLim) / 2;
		xpT = _XpFromCb(cb, fHex);
		if (xpT < xpFind)
			cbMin = cb + 1;
		else
			cbLim = cb;
		}
	if (_fFixed && cbMin == _cbLine)
		cbMin--;
	ib = cbMin + _cbLine * _LnFromYp(yp);
	ib = LwMin(ib, _pbsf->IbMac());
	if (pvNil != pfRight)
		*pfRight = cbMin == _cbLine;
	return ib;
}


/***************************************************************************
	Handle a mouse down in our content.
***************************************************************************/
void DCH::MouseDown(long xp, long yp, long cact, ulong grfcust)
{
	AssertThis(0);
	bool tHex;
	bool fDown, fRight;
	PT pt, ptT;
	long ib;
	RC rc;

	//doing this before the activate avoids flashing the old selection
	tHex = tMaybe;
	ib = _IbFromPt(xp, yp, &tHex, &fRight);
	if (ivNil != ib)
		_SetSel((grfcust & fcustShift) ? _ibAnchor : ib, ib, fRight);

	if (!_fActive)
		Activate(fTrue);

	if (ivNil == ib)
		return;

	_SetHexSel(tYes == tHex);

	Clean();
	_GetContent(&rc);
	for (GetPtMouse(&pt, &fDown); fDown; GetPtMouse(&pt, &fDown))
		{
		if (!rc.FPtIn(pt.xp, pt.yp))
			{
			//do autoscroll
			ptT = pt;
			rc.PinPt(&pt);
			_Scroll(scaToVal, scaToVal,
				_scvHorz + LwDivAway(ptT.xp - pt.xp, _dxpChar),
				_scvVert + LwDivAway(ptT.yp - pt.yp, _dypLine));
			}

		ib = _IbFromPt(pt.xp, pt.yp, &tHex, &fRight);
		if (ivNil != ib)
			_SetSel(_ibAnchor, ib, fRight);
		}
}


/***************************************************************************
	Return the maximum for the indicated scroll bar.
***************************************************************************/
long DCH::_ScvMax(bool fVert)
{
	RC rc;

	_GetContent(&rc);
	return LwMax(0, fVert ?
		(_pbsf->IbMac() + _cbLine - 1) / _cbLine + 1 - rc.Dyp() / _dypLine :
		_IchFromCb(_cbLine, fFalse) + 2 - rc.Dxp() / _dxpChar);
}


/***************************************************************************
	Copy the selection.
***************************************************************************/
bool DCH::_FCopySel(PDOCB *ppdocb)
{
	PDHEX pdhex;
	long ib1, ib2;

	ib1 = _ibOther;
	ib2 = _fFixed ? _pbsf->IbMac() : _ibAnchor;
	if (_ibOther == ib2)
		return fFalse;

	if (pvNil == ppdocb)
		return fTrue;

	SortLw(&ib1, &ib2);
	if (pvNil != (pdhex = DHEX::PdhexNew()))
		{
		if (!pdhex->Pbsf()->FReplaceBsf(_pbsf, ib1, ib2 - ib1, 0, 0))
			ReleasePpo(&pdhex);
		}

	*ppdocb = pdhex;
	return pvNil != *ppdocb;
}


/***************************************************************************
	Clear (delete) the selection.
***************************************************************************/
void DCH::_ClearSel(void)
{
	_FReplace(pvNil, 0, _ibAnchor, _ibOther, fFalse);
}


/***************************************************************************
	Paste over the selection.
***************************************************************************/
bool DCH::_FPaste(PCLIP pclip, bool fDoIt, long cid)
{
	AssertThis(0);
	AssertPo(pclip, 0);
	long ib1, ib2, cb;
	PDOCB pdocb;
	PBSF pbsf;

	if (cidPaste != cid)
		return fFalse;

	if (!pclip->FGetFormat(kclsDHEX) && !pclip->FGetFormat(kclsTXTB))
		return fFalse;

	if (!fDoIt)
		return fTrue;

	if (pclip->FGetFormat(kclsDHEX, &pdocb))
		{
		if (pvNil == (pbsf = ((PDHEX)pdocb)->Pbsf()) || 0 >= (cb = pbsf->IbMac()))
			{
			ReleasePpo(&pdocb);
			return fFalse;
			}
		}
	else if (pclip->FGetFormat(kclsTXTB, &pdocb))
		{
		if (pvNil == (pbsf = ((PTXTB)pdocb)->Pbsf()) ||
				0 >= (cb = pbsf->IbMac() - size(achar)))
			{
			ReleasePpo(&pdocb);
			return fFalse;
			}
		}
	else
		return fFalse;

	ib1 = _ibAnchor;
	ib2 = _ibOther;
	_SwitchSel(fFalse);
	SortLw(&ib1, &ib2);
	if (_fFixed)
		{
		cb = LwMin(cb, _pbsf->IbMac() - ib1);
		ib2 = ib1 + cb;
		}
	if (!_pbsf->FReplaceBsf(pbsf, 0, cb, ib1, ib2 - ib1))
		{
		ReleasePpo(&pdocb);
		return fFalse;
		}

	_InvalAllDch(ib1, cb, ib2 - ib1);
	ib1 += cb;
	_SetSel(ib1, ib1, fFalse /*REVIEW shonk*/);
	_ShowSel();

	ReleasePpo(&pdocb);
	return fTrue;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of an object.
***************************************************************************/
void DCH::AssertValid(ulong grf)
{
	DCH_PAR::AssertValid(0);
	AssertPo(_pbsf, 0);
	AssertIn(_cbLine, 1, kcbMaxLineDch + 1);
	AssertIn(_ibAnchor, 0, kcbMax);
	AssertIn(_ibOther, 0, kcbMax);
}


/***************************************************************************
	Mark memory for the DCH.
***************************************************************************/
void DCH::MarkMem(void)
{
	AssertValid(0);
	DCH_PAR::MarkMem();
	MarkMemObj(_pbsf);
}
#endif //DEBUG


/***************************************************************************
	Constructor for a chunk hex editing doc.
***************************************************************************/
DOCH::DOCH(PDOCB pdocb, PCFL pcfl, CTG ctg, CNO cno)
	: DOCE(pdocb, pcfl, ctg, cno)
{
}


/***************************************************************************
	Creates a new hex editing doc based on the given chunk.  Asserts that
	there are no open editing docs based on the chunk.
***************************************************************************/
PDOCH DOCH::PdochNew(PDOCB pdocb, PCFL pcfl, CTG ctg, CNO cno)
{
	AssertPo(pdocb, 0);
	AssertPo(pcfl, 0);

	Assert(pvNil == DOCE::PdoceFromChunk(pdocb, pcfl, ctg, cno),
		"DOCE already exists for the chunk");
	PDOCH pdoch;

	if (pvNil == (pdoch = NewObj DOCH(pdocb, pcfl, ctg, cno)))
		return pvNil;
	if (!pdoch->_FInit())
		{
		ReleasePpo(&pdoch);
		return pvNil;
		}
	AssertPo(pdoch, 0);
	return pdoch;
}


/***************************************************************************
	Initialize the stream from the given flo.
***************************************************************************/
bool DOCH::_FRead(PBLCK pblck)
{
	FLO flo;
	bool fRet;

	if (!pblck->FUnpackData())
		return fFalse;

	if (pvNil == (flo.pfil = FIL::PfilCreateTemp()))
		return fFalse;
	flo.fp = 0;
	flo.cb = pblck->Cb();

	if (!pblck->FWriteToFlo(&flo))
		{
		ReleasePpo(&flo.pfil);
		return fFalse;
		}
	fRet = _bsf.FReplaceFlo(&flo, fFalse, 0, _bsf.IbMac());
	ReleasePpo(&flo.pfil);

	return fRet;
}


/***************************************************************************
	Create a new DDG for the doc.
***************************************************************************/
PDDG DOCH::PddgNew(PGCB pgcb)
{
	AssertThis(0);
	return DCH::PdchNew(this, &_bsf, fFalse, pgcb);
}


/***************************************************************************
	Returns the length of the data on file
***************************************************************************/
long DOCH::_CbOnFile(void)
{
	AssertThis(0);
	return _bsf.IbMac();
}


/***************************************************************************
	Writes the data and returns success/failure.
***************************************************************************/
bool DOCH::_FWrite(PBLCK pblck, bool fRedirect)
{
	AssertThis(0);
	if (!_bsf.FWriteRgb(pblck))
		return fFalse;
	_FRead(pblck);
	return fTrue;
}


#ifdef DEBUG
/***************************************************************************
	Assert the validity of an object.
***************************************************************************/
void DOCH::AssertValid(ulong grf)
{
	DOCH_PAR::AssertValid(0);
	AssertPo(&_bsf, 0);
}


/***************************************************************************
	Mark memory used by the DOCH.
***************************************************************************/
void DOCH::MarkMem(void)
{
	AssertThis(0);
	DOCH_PAR::MarkMem();
	MarkMemObj(&_bsf);
}
#endif //DEBUG

